
;       定义一些常量,这些都是内核里面的寄存器,等下触发PendSV异常会用到
SCB_VTOR            EQU     0xE000ED08  ;   Vector Table Offset Register
NVIC_INT_CTRL       EQU     0xE000ED04  ;   Interrupt Control State Register
NVIC_SYSPRI2        EQU     0xE000ED20  ;   System Priority Register (2)
NVIC_PENDSV_PRI     EQU     0x00FF0000  ;   PendSV Priority Value (Lowest)
NVIC_PENDSVSET      EQU     0x10000000  ;   value To Trigger PendSV Exception

    AREA | .text |, CODE, READONLY, ALIGN=2   ; AREA和CODE合起来表示汇编一个新的代码段  因为.text开头是以.开始的,故段名字得写成|.text|  ALIGN=2表示按照2^2字节对齐
    THUMB                                     ;表示THUMB指令代码
    REQUIRE8                                  ;
    PRESERVE8                                 ;和上面的REQUIRE8  均表示当前文件的栈按照8字节对齐

    IMPORT  rt_thread_switch_interrupt_flag   ;使用关键字导入一些全局变量,这上三个变量在cpuport.c中定义
    IMPORT  rt_interrupt_from_thread
    IMPORT  rt_interrupt_to_thread

;/*
: * rt_base_t rt_hw_interrupt_disable();
; */
rt_hw_interrupt_disable     PROC              ;用于定义子程序,与ENDP成对使用
    EXPORT  rt_hw_interrupt_disable
    MRS     r0,PRIMASK                        ;通过MRS指令将特殊寄存器PRIMASK的值存储到通用寄存器r0 r0作为函数的返回值
    CPSID   I                                 ;CPS指令将PRIMASK寄存器的值置1
    BX      LR                                ;跳转到由寄存器给定的地址(LR给定)
    ENDP

;/*
: * void rt_hw_interrupt_enable(rt_base_t level);
; */
rt_hw_interrupt_enable      PROC
    EXPORT  rt_hw_interrupt_enable
    MRS     PRIMASK,r0                       ;r0作为函数的形参,对应level
    BX      LR
    ENDP

;/*
; *void rt_hw_context_switch(rt_uint32 from,rt_uint32 to)
; *r0 --> from          当一个汇编函数在C文件中调用的时候,如果有两个形参  则执行的时候,会将这个形参传入到CPU寄存器r0,r1
; *r1 --> to
; */
rt_hw_context_switch_interrupt
    EXPORT  rt_hw_context_switch_interrupt
rt_hw_context_switch        PROC
    EXPORT  rt_hw_context_switch

    ;set rt_thread_switch_interrupt_flag to 1                   //好记性不如烂笔头！！！！！！！！！！！
    LDR     r2,=rt_thread_switch_interrupt_flag         ;将rt_thread_switch_interrupt_flag的地址值加载到r2
    LDR     r3,[r2]                                     ;和上面的联合起来就是:将rt_thread_switch_interrupt_flag的值存进r3
    CMP     r3,#1                                       ;将r3与立即数1比较,相等则执行BEQ指令,否则不执行
    BEQ     _reswitch                                   ;跳转到标号 _reswitch  前提是r3和#1相等
    MOV     r3,#1                                       ;将立即数1装入r3中  走到这里说明rt_thread_switch_interrupt_flag的值为0
    STR     r3,[r2]                                     ;将r3的值存进rt_thread_switch_interrupt_flag  既让其等于1
;若rt_thread_switch_interrupt_flag的值不为1,我们就让其为1
    LDR     r2,=rt_interrupt_from_thread                ;将rt_interrupt_from_thread变量的地址存入r2中
    STR     r0,[r2] ;[r2]表示rt_interrupt_from_thread   ;将r0的值存入到变量rt_interrupt_from_thread中,即上一个线程栈指针sp的指针
                                                        ;(*rt_interrupt_from_thread)表示sp    (*(*rt_interrupt_from_thread))表示sp指向的内容
_reswitch        ;此时表示rt_thread_switch_interrupt_flag的值等于1
    LDR     r2,=rt_interrupt_to_thread                  ;将rt_interrupt_to_thread变量的地址存入r2中
    STR     r1,[r2]                                     ;将r1的值存入rt_interrupt_to_thread中,即下一个线程栈指针sp的指针

    LDR     r0,=NVIC_INT_CTRL                           ;将变量NVIC_INT_CTRL的地址存入r0中
    LDR     r1,=NVIC_PENDSVSET                          ;将变量NVIC_PENDSVSET的地址存入r1中
    STR     r1,[r0]                                     ;触发PendSV异常,实现上下文切换
    
    BX      LR                                          ;返回到调用rt_hw_context_switch()函数的地方
    ENDP        ;注意这个函数调用的时候,总中断是关闭的

;PendSV可悬起异常,其会自动延迟上下文切换的请求,直到其它的ISR都完成了处理后才放行

;r0 -->switch from thread stack   这里的参数传递是在rt_hw_context_switch函数中完成的
;r1 -->switch to thread stack
;psr, pc, lr, r12, r3, r2, r1, r0, are pushed into [from] stack
PendSV_Handler  PROC        ;此函数被重写了
    EXPORT  PendSV_Handler
    
    ;disable interrupt to protect context switch        下面的两条指令,就是保存当前中断的状态;然后关中断
    MRS     r2,PRIMASK                                  ;将PRIMASK的值存进r2
    CPSID   I                                           ;CPS指令将PRIMASK寄存器的值置1(关中断)

    ;get rt_thread_switch_interrupt_flag
    LDR     r0,=rt_thread_switch_interrupt_flag         ;将rt_thread_switch_interrupt_flag变量的地址加载到r0中
    LDR     r1,[r0]                                     ;将rt_thread_switch_interrupt_flag的值放入r1中
    CBZ     r1,pendsv_exit                              ;判断r1的值是否为0,如果是则立马跳转到pendsv_exit  r1的值等于rt_thread_switch_interrupt_flag
    ;只有当rt_thread_switch_interrupt_flag的值为1时,pendsv_exit这个中断函数才执行相应的功能
    ;clear rt_thread_switch_interrupt_flag to 0
    MOV     r1,#0x00
    STR     r1,[r0]                                     ;将立即数0放入rt_thread_switch_interrupt_flag中(即清零)

    LDR     r0,=rt_interrupt_from_thread                ;将rt_interrupt_from_thread变量的地址加载到r0中
    LDR     r1,[r0]                                     ;将rt_interrupt_from_thread变量的值放入r1中
    CBZ     r1,switch_to_thread                         ;若r1的值为零,则跳到switch_to_thread处(第一次线程切换时rt_interrupt_from_thread肯定为0)
;往下面走是因为r1(rt_interrupt_from_thread)的值为1 
;=========================================================上文保存========================================================================
    ;当进入到PendSV_Handler时,上一个线程运行的环境即:
    ;xPSR或PSRs(程序状态寄存器),PC(线程入口地址),R14(LR:链接寄存器,调用子程序时,由R14存储返回地址)
    ;R12,R3,R2,R1,R0(线程的形参),这些CPU寄存器的值会自动保存到线程的栈中,剩下的r4~r11需要手动保存

    ;R0~R12通用寄存器   R13(SP堆栈指针)   R15(PC:程序计数器寄存器 修改它就可以改变程序的执行流)   r0~r3用作传入函数参数  传出函数返回值
    MRS     r1,psp      ;获取线程栈指针到r1    R13是堆栈指针  在CM3处理器内核中共有两个堆栈指针  当引用R13时(写作SP)你引用到的是当前正在使用的那一个
;此时r1里面存的是rt_interrupt_from_thread的值  ;另一个必须用特殊指令来访问(MSR,MRS指令)   这两个堆栈指针分别是：
                        ;主堆栈指针(MSP) 
                        ;进程堆栈指针(PSP)  //MSR  MRS   ;要悄无声息,让懂你得人懂你,让不懂你的人不懂你                              sp的地址会减小
    STMFD   r1!,{r4-r11}                                ;push r4~r11 register   将CPU寄存器r4~r11的值存储到r1指定的地址,先r11后r4(每操作一次r1里面存储地址将递减一次 由高位到底位)
    LDR     r0,[r0]                                     ;将r0指向的变量的值放到r0中  (即将rt_interrupt_from_thread的值放到r0中)
    STR     r1,[r0]                                     ;将r1的值存储到rt_interrupt_from_thread所指向的变量中(即更新线程栈指针sp栈顶位置发生了变化)  [rt_interrupt_from_thread]表示sp
;==========================================================下文切换=======================================================================
switch_to_thread        ;相当于一个标号
    LDR     r1,=rt_interrupt_to_thread                  ;将rt_interrupt_to_thread的地址放入r1中
    LDR     r1,[r1]                                     ;将rt_interrupt_to_thread的值放入r1中  完事儿后r1等于sp指针的指针
    LDR     r1,[r1]                                     ;将此时r1里面存的是sp

    LDMFD   r1!,{r4-r11}                                ;pop r4-r11 register 将线程栈指针r1指向的内容加载到CPU寄存器r4~r11  
    MSR     psp,r1                                      ;将线程栈指针更新到psp(实时更新位置)     sp的地址会增加

pendsv_exit
    ;恢复中断
    MSR     PRIMASK,r2                                  ;将存在r2中的值还给PRIMASK
    ORR     LR,LR,#0x04                                 ;确保异常返回使用的栈指针是PSP,即LR寄存器的位2要为1
                                                        ;LR的位2为1表示FAULT发生时使用的是PSP  为0表示使用的是MSP
    BX      LR          ;异常返回,此时栈中的剩下内容将会自动加载到CPU寄存器(即xPSR PC R14 R12 R3 R2 R1 R0)
    ENDP

;/*
; * void rt_hw_context_switch_to(rt_uint32_t to)  这个函数只调用一次(最开始的时候调用)
; * r0-->to    因为只有一个参数,故参数得传给r0
; * this function is used to perform the first thread switch        变得更强生于忧患死于忧患 不接触没事儿,一接触直接爆炸！
; */
rt_hw_context_switch_to     PROC
    EXPORT  rt_hw_context_switch_to
    ;set to thread
    LDR     r1,=rt_interrupt_to_thread          ;将rt_interrupt_to_thread的地址值放入r1中
    STR     r0,[r1]                             ;将to_thread->sp的地址传给变量rt_interrupt_to_thread

    ;set from thread to 0
    LDR     r1,=rt_interrupt_from_thread
    MOV     r0,#0x0   ;这里r0的使命已经完成了,故可以添加新的值给他
    STR     r0,[r1]   ;将rt_interrupt_from_thread的值清零
    
    ;set interrupt flag to 1   不妨停下来多思考思考   
    LDR     r1,=rt_thread_switch_interrupt_flag
    MOV     r0,#1
    STR     r0,[r1]   ;将变量rt_thread_switch_interrupt_flag的值变为1

    ;set the PendSV exception priority  将PendSV异常的优先级设置为最低
    LDR     r0,=NVIC_SYSPRI2
    LDR     r1,=NVIC_PENDSV_PRI
    LDR.W   r2,[r0,#0x00]                      ;read
    ORR     r1,r1,r2                           ;modify
    STR     r1,[r0]                            ;write-back

    ;trigger the PendSV exception (cause context switch)
    LDR     r0,=NVIC_INT_CTRL           ;取地址或者立即数
    LDR     r1,=NVIC_PENDSVSET
    STR     r1,[r0]

    ;restore MSP
    LDR     r0,=SCB_VTOR
    LDR     r0,[r0]                     ;将地址SCB_VTOR处存放的值放入r0中
    LDR     r0,[r0]                     ;
    MSR     msp,r0                      ;将r0中的值放入msp中
    
    ;enable interrupts at processor level
    CPSIE   F 
    CPSIE   I 
    ;never reach here
    ENDP

;compatible with old version
rt_hw_interrupt_thread_switch   PROC
    EXPORT  rt_hw_interrupt_thread_switch
    BX      lr              ;直接返回
    ENDP    

    IMPORT  rt_hw_hard_fault_exception      ;这是一个外部函数
    EXPORT  HardFault_Handler   ;为毛要写在PROC的上面
HardFault_Handler    PROC

    ; get current context
    TST     lr, #0x04               ; if(!EXC_RETURN[2])
    MRSNE   r0, msp                 ; get fault context from handler.
    MRSEQ   r0, psp                 ; get fault context from thread.

    STMFD   r0!, {r4 - r11}         ; push r4 - r11 register
    STMFD   r0!, {lr}               ; push exec_return register

    MSRNE   msp, r0                 ; update stack pointer to MSP.
    MSREQ   psp, r0                 ; update stack pointer to PSP.

    PUSH    {lr}
    BL      rt_hw_hard_fault_exception
    POP     {lr}

    ORR     lr, lr, #0x04
    BX      lr
    ENDP

    ALIGN   4

    END  